import json
import time

"""
Author: Gaoqiang Du
Description：common Classes/methods
Date: 2022/1/29 16:52
"""


class Snow:
    """Snowflakes algorithm to generate UUID"""

    init_date = time.strptime('2020-01-01 00:00:00', "%Y-%m-%d %H:%M:%S")
    start = int(time.mktime(init_date) * 1000)
    last = int(time.time() * 1000)
    pc_room = 1
    pc = 1
    seq = 0

    @classmethod
    async def get_id(cls):
        now = int(time.time() * 1000)
        if now != cls.last:
            cls.last = now
            cls.seq = 1
        else:
            while cls.seq >= 4096:
                time.sleep(0.1)
                return cls.get_id()
            cls.seq += 1

        time_diff = now - cls.start
        pk = (time_diff << 22) ^ (cls.pc_room << 18) ^ (cls.pc << 12) ^ cls.seq

        return pk


class OrderSnow(Snow):
    pc_room = 1
    pc = 1
    seq = 0


class UniqCode:
    """
    Generating a unique code from the ID.
    It can be used in the scenario of invitation codes or share codes.
    Encryption and decryption are performed locally
    """
    # Random char, used for confusion
    CHARS = ('F', 'L', 'G', 'W', '5', 'X', 'C', '3', '9', 'Z', 'M', '6', '7',
             'Y', 'R', 'T', '2', 'H', 'S',
             '8', 'D', 'V', 'E', 'J', '4', 'K', 'Q', 'P', 'U', 'A', 'N', 'B')
    CHARS_LEN = 32

    # length of unique code
    CODE_LEN = 7
    # random number, salt
    SALT = 131420

    # next is diffusion
    # PRIME1 and the length(L) of CHARS are prime Numbers,
    # this can ensure ( id * PRIME1) % L to evenly distribute in [0,L)
    PRIME1 = 3
    # PRIME2 and CODE_LENGTH are prime Numbers,
    # this can ensure ( index * PRIME2) % CODE_LENGTH to evenly distribute
    # in [0，CODE_LENGTH)
    PRIME2 = 9

    def __new__(cls, *args, **kwargs):
        """singleton pattern"""
        if not hasattr(cls, "_instance"):
            cls._instance = super(UniqCode, cls).__new__(cls)
        return cls._instance

    @classmethod
    async def encode(cls, num: int) -> str:
        # diffusion and salting
        num = num * cls.PRIME1 + cls.SALT
        # encryption
        b = [num] + [0 for _ in range(cls.CODE_LEN - 1)]
        for i in range(5):
            b[i + 1] = b[i] // cls.CHARS_LEN
            b[i] = (b[i] + b[0] * i) % cls.CHARS_LEN

        # The last two digits act as a check
        b[5] = (b[0] + b[1] + b[2]) * cls.PRIME1 % cls.CHARS_LEN
        b[6] = (b[3] + b[4] + b[5]) * cls.PRIME1 % cls.CHARS_LEN

        code = ""
        for i in range(cls.CODE_LEN):
            # confusion
            code += cls.CHARS[b[(i * cls.PRIME2) % cls.CODE_LEN]]

        return code

    @classmethod
    async def decode(cls, code: str) -> int:
        """
        decryption，The return value -1 indicates that the authentication fails
        """

        # check length
        if len(code) != cls.CODE_LEN:
            return -1

        num = 0
        a = [0 for _ in range(cls.CODE_LEN)]
        b = [0 for _ in range(cls.CODE_LEN)]

        for i in range(cls.CODE_LEN):
            a[(i * cls.PRIME2) % cls.CODE_LEN] = i

        try:
            for i in range(cls.CODE_LEN):
                a[i] = cls.CHARS.index(code[a[i]])
        except ValueError:
            return -1

        # check the last two digits
        b[5] = (a[0] + a[1] + a[2]) * cls.PRIME1 % cls.CHARS_LEN
        b[6] = (a[3] + a[4] + a[5]) * cls.PRIME1 % cls.CHARS_LEN
        if a[5] != b[5] or a[6] != b[6]:
            return -1

        for i in range(4, -1, -1):
            b[i] = (a[i] - a[0] * i + cls.CHARS_LEN * i) % cls.CHARS_LEN

        for i in range(4, 0, -1):
            num = (num + b[i]) * cls.CHARS_LEN

        num = ((num + b[0]) - cls.SALT) // cls.PRIME1
        return num


class SampleJsonEncoder(json.JSONEncoder):
    """Forced serialization."""

    def default(self, obj):
        try:
            return json.JSONEncoder.default(self, obj)
        except TypeError:
            return str(obj)
